package cn.gs.exchanger.agent;

// @author gs
import cn.gs.exchanger.ExchangerClient;
import cn.gs.exchanger.ExchangerClient.IService;
import cn.gs.exchanger.ExchangerException;
import cn.gs.serializable.entity.EntityController;
import cn.gs.serializable.serialize.ClassSerialize;
import cn.gs.serializable.serialize.SerializeController;
import cn.gs.util.set.Pair;
import cn.gs.reflect.ClassTool;
import cn.gs.reflect.ProxyTool;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AgentClient implements IAgentClient {

    <T> T newAgent(String id, String server, Class<T> clazz) {
        if (id != null && server != null) {
            if (clazz != null) {
                Agent p = new Agent(id, server, this, clazz);
                T t = (T) p.proxyObject();
                return t;
            }
        }
        return null;
    }

    @Override
    public <T> T create(Class<T> clazz) throws ExchangerException {
        if (cls.contains(clazz)) {
            Pair<List<String>, ExchangerException> s = exchanger.ask(CAgent.CREATE + clazz.getCanonicalName(), clientId);
            if (s != null) {
                if (s.getSecond() != null) {
                    Logger.getLogger(AgentClient.class.getName()).log(Level.INFO, null, s.getSecond());
                    throw s.getSecond();
                }
                if (s.getFirst() != null && s.getFirst().size() >= 2) {
                    T t = newAgent(s.getFirst().get(0), s.getFirst().get(1), clazz);
                    return t;
                }
            }
        }
        return null;
    }

    public void forceRemove(Object o) throws ExchangerException {
        if (o != null) {
            Object target = ProxyTool.getProxyTarget(o);
            if (target != null && target instanceof Agent) {
                Agent agent = (Agent) target;
                if (cls.contains(agent.forProxy)) {
                    addRemoveQueue(agent.getServer(), agent.getId());
                    forceRemoveAll();
                }
            }
        }
    }

    public void forceRemoveAll() throws ExchangerException {
        Pair<String, String> p;
        Map<String, Set<String>> rm = new HashMap<String, Set<String>>();
        while ((p = removeQueue.poll()) != null) {
            Set<String> ss = rm.get(p.getFirst());
            if (ss == null) {
                ss = new HashSet<String>();
                rm.put(p.getFirst(), ss);
            }
            ss.add(p.getSecond());
        }
        for (Entry<String, Set<String>> e : rm.entrySet()) {
            try {
                List<String> args = new LinkedList(e.getValue());
                Pair<List<String>, ExchangerException> s = exchanger.ask(CAgent.DELETE + "@" + e.getKey(), args);
                if (s != null && s.getSecond() != null) {
                    Logger.getLogger(AgentClient.class.getName()).log(Level.INFO, null, s.getSecond());
                    throw s.getSecond();
                }
            } catch (ExchangerException ex) {
            }
        }
    }

    void addRemoveQueue(String server, String id) {
        removeQueue.offer(new Pair(server, id));
    }

    @Override
    public void remove(Object o) throws ExchangerException {
        if (o != null) {
            Object target = ProxyTool.getProxyTarget(o);
            if (target != null && target instanceof Agent) {
                Agent agent = (Agent) target;
                if (cls.contains(agent.forProxy)) {
                    addRemoveQueue(agent.getServer(), agent.getId());
                }
            }
        }
    }

    public <T> T noProxy(String server, String id, Class<T> c) throws ExchangerException {
        Pair<List<String>, ExchangerException> s = exchanger.ask(CAgent.NO_PROXY + "@" + server, id);
        if (s != null && s.getSecond() != null) {
            throw s.getSecond();
        }
        if (s != null && s.getFirst() != null && s.getFirst().size() >= 2) {
            //0: Object 1:Class
            Class clz = clzs.forClass(s.getFirst().get(1));
            T t = (T) SerializeController.staticToObject(s.getFirst().get(0), clz == null ? c : clz);
            return t;
        }
        return null;
    }

    @Override
    public <T> T noProxy(T o) throws ExchangerException {
        if (o != null && ClassTool.isAssignableFrom(cls, o.getClass())) {
            Object target = ProxyTool.getProxyTarget(o);
            if (target != null && target instanceof Agent) {
                Agent agent = (Agent) target;
                T t = (T) noProxy(agent.getServer(), agent.getId(), o.getClass());
                if (t != null) {
                    return t;
                }
            }
        }
        return null;
    }

    @Override
    public <T> T invoke(String id, Class<T> retClazz, String serverName, Object... args) throws ExchangerException {
        List<String> ag = new ArrayList<String>(args.length + 1);
        //Convert Args to String
        ag.add(clientId);
        ag.add(id);
        EntityController ec = new EntityController();
        for (int i = 0; i < args.length; ++i) {
            String s = sc.toString(args[i]);
            ag.add(s);
        }
        //Ask
        Pair<List<String>, ExchangerException> ans = exchanger.ask(serverName, ag);
        //Convet answer to Object
        if (ans != null) {
            if (ans.getSecond() != null) {
                throw ans.getSecond();
            }
            if (ans.getFirst() != null && ans.getFirst().size() >= 1) {
                T t = sc.toObject(ans.getFirst().get(0), retClazz);
                return t;
            }
        }
        throw ExchangerException.Type.UNKNOWN_EXCEPTION.create("Ask_no_reply.", null);
    }
    String clientId = UUID.randomUUID().toString();
    SerializeController sc = new SerializeController(new ClientAgentSerialize(this));
    ExchangerClient exchanger;
    ClassSerialize clzs = new ClassSerialize();
    Queue<Pair<String, String>> removeQueue = new ConcurrentLinkedQueue<Pair<String, String>>();
    //Map<String, WeakHashMap<String, Object>> objs = new HashMap<String, WeakHashMap<String, Object>>();

    public AgentClient(ExchangerClient exchanger) throws ExchangerException {
        this.exchanger = exchanger;
        exchanger.register(CAgent.LIVE + "@" + clientId, new LiveProcess(this), false);
    }
    Set<Class> cls = new HashSet<Class>();

    public final void add(Class... cls) {
        this.cls.addAll(Arrays.asList(cls));
    }
}

class LiveProcess implements IService {

    ExchangerClient exchanger;
    WeakReference<AgentClient> client;
    String clientId;

    LiveProcess(AgentClient client) {
        this.clientId = client.clientId;
        this.exchanger = client.exchanger;
        this.client = new WeakReference<AgentClient>(client);
    }

    @Override
    public List<String> process(List<String> args) throws ExchangerException {
        AgentClient c = client.get();
        if (c == null) {
            exchanger.delete(CAgent.LIVE + "@" + clientId);
            throw ExchangerException.Type.SERVER_NOT_EXIST.create(CAgent.LIVE + "@" + clientId + "_failed.", null);
        } else {
            //Remove Objects
            c.forceRemoveAll();
            //Return
            return Arrays.asList("true");
        }
    }
}
